// SPDX-License-Identifier: GPL-2.0-or-later

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#include <QAbstractSocket>

#include "fqterm.h"
#include "fqterm_telnet.h"
#include "fqterm_socket.h"
#include "fqterm_telnet_socket.h"
#include "fqterm_local_socket.h"

#ifndef _NO_SSH_COMPILED
#include "fqterm_ssh_socket.h"
#endif

/* TELNET Command Codes: */
/* Hints: These NVT control characters are sent from client to server, So
   the client side will not receive these commands */
#define TCSB		(u_char)250	/* Start Subnegotiation		*/
#define TCSE		(u_char)240	/* End Of Subnegotiation	*/
#define TCNOP		(u_char)241	/* No Operation			*/
#define TCDM		(u_char)242	/* Data Mark (for Sync)		*/
#define TCBRK		(u_char)243	/* NVT Character BRK		*/
#define TCIP		(u_char)244	/* Interrupt Process		*/
#define TCAO		(u_char)245	/* Abort Output			*/
#define TCAYT		(u_char)246	/* "Are You There?" Function	*/
#define TCEC		(u_char)247	/* Erase Character		*/
#define TCEL		(u_char)248	/* Erase Line			*/
#define TCGA		(u_char)249	/* "Go Ahead" Function		*/
#define TCWILL		(u_char)251	/* Desire/Confirm Will Do Option*/
#define TCWONT		(u_char)252	/* Refusal To Do Option		*/
#define TCDO		(u_char)253	/* Request To Do Option		*/
#define TCDONT		(u_char)254	/* Request NOT To Do Option	*/
#define TCIAC		(u_char)255	/* Interpret As Command Escape	*/

/* Telnet Option Codes: */
#define TOTXBINARY	(u_char)  0	/* TRANSMIT-BINARY option	*/
#define TOECHO		(u_char)  1	/* ECHO Option			*/
#define TONOGA		(u_char)  3	/* Suppress Go-Ahead Option	*/
#define TOTERMTYPE	(u_char) 24	/* Terminal-Type Option		*/
#define TONAWS		(u_char) 31	/* Window  Size */

/* Network Virtual Printer Special Characters: */
/* In normal situations, these characters will to translated into local
   control characters , then pass to upper layer term. But in our situation,
   we can pass them to term directly */

#define VPLF		'\n'	/* Line Feed				*/
#define VPCR		'\r'	/* Carriage Return			*/
#define VPBEL		'\a'	/* Bell (attention signal)		*/
#define VPBS		'\b'	/* Back Space				*/
#define VPHT		'\t'	/* Horizontal Tab			*/
#define VPVT		'\v'	/* Vertical Tab				*/
#define VPFF		'\f'	/* Form Feed				*/

/* Keyboard Command Characters: */

/* Option Subnegotiation Constants: */
#define TT_IS		0	/* TERMINAL_TYPE option "IS" command	*/
#define TT_SEND		1	/* TERMINAL_TYPE option "SEND" command	*/

/* Telnet Socket-Input FSM States: */
// 网络虚拟终端（NVT，Network Virtual Terminal）的定义
// 只要客户机或服务器要发送命令序列而不是数据流，它就在数据流中插入一个特殊的保留字符，该保留字符叫做“解释为命令”（IAC  ，Interpret As Command)
/*
名称      编码  说明
EOF      236  文件结束符
SUSP     237  挂起当前进程
ABORT    238  中止进程
EOR      239  记录结束符
SE       240  子选项结束
NOP      241  空操作
DM       242  数据标记
BRK      243  终止符（break）
IP       244  终止进程
AO       245  终止输出
AYT      246  请求应答
EC       247  终止符
EL       248  擦除一行
GA       249  继续
SB       250  子选项开始
WILL     251  选项协商,指示希望开始执行，或者确认现在正在操作指示的选项。
WONT     252  选项协商,指出拒绝执行或继续招待所指示的选项
DO       253  选项协商,指出要求对方执行，或者确认希望对方执行指示的选项
DONT     254  选项协商,指出要求对方停止执行，或者确诊要求对方停止执行指示的选项。
IAC      255  字符0XFF
*/
#define TSDATA		 0	// normal data processing
#define TSIAC		 1	// have seen IAC
#define TSWOPT		 2	// have seen IAC-{WILL/WONT}
#define TSDOPT		 3	// have seen IAC-{DO/DONT}
#define TSSUBNEG	 4	// have seen IAC-SB
#define TSSUBIAC	 5	// have seen IAC-SB-...-IAC
// if any state added here, please update NTSTATES (currently 6).

// Telnet Option Subnegotiation FSM States:
#define SS_START	0		// initial state
#define SS_TERMTYPE	1		// TERMINAL_TYPE option subnegotiation
#define SS_END		2		// state after all legal input
// if any state added here, please update NSSTATES (currently 3).

#define FSINVALID	0xff		// an invalid state number
#define TCANY		(NCHRS+1)	// match any character

#define TINVALID	0xff		// an invalid transition index

namespace FQTerm {

struct fsm_trans FQTermTelnet::ttstab[] =  {
	/* State	Input		Next State	Action	*/
	/* ------	------		-----------	-------	*/
    { TSDATA,   TCIAC,      TSIAC,      &FQTermTelnet::no_op },
    { TSDATA,   TCANY,      TSDATA,     &FQTermTelnet::ttputc },
    { TSIAC,    TCIAC,      TSDATA,     &FQTermTelnet::ttputc },
    { TSIAC,    TCSB,       TSSUBNEG,   &FQTermTelnet::no_op },
    { TSIAC,    TCNOP,      TSDATA,     &FQTermTelnet::no_op }, /* Telnet Commands */
    { TSIAC,    TCDM,       TSDATA,     &FQTermTelnet::tcdm },
    { TSIAC,    TCWILL,     TSWOPT,     &FQTermTelnet::recopt },
    { TSIAC,    TCWONT,     TSWOPT,     &FQTermTelnet::recopt },
    { TSIAC,    TCDO,       TSDOPT,     &FQTermTelnet::recopt },
    { TSIAC,    TCDONT,     TSDOPT,     &FQTermTelnet::recopt },
    { TSIAC,    TCANY,      TSDATA,     &FQTermTelnet::no_op },
    { TSSUBNEG, TCIAC,      TSSUBIAC,   &FQTermTelnet::no_op },
    { TSSUBNEG, TCANY,      TSSUBNEG,   &FQTermTelnet::subopt },
    { TSSUBIAC, TCSE,       TSDATA,     &FQTermTelnet::subend },
    { TSSUBIAC, TCANY,      TSSUBNEG,   &FQTermTelnet::subopt },
    { TSWOPT,   TOECHO,     TSDATA,     &FQTermTelnet::do_echo },
    { TSWOPT,   TONOGA,     TSDATA,     &FQTermTelnet::do_noga },
    { TSWOPT,   TOTXBINARY, TSDATA,     &FQTermTelnet::do_txbinary },
    { TSWOPT,   TCANY,      TSDATA,     &FQTermTelnet::do_notsup },
    { TSDOPT,   TONAWS,     TSDATA,     &FQTermTelnet::will_naws },
    { TSDOPT,   TOTERMTYPE, TSDATA,     &FQTermTelnet::will_termtype },
    { TSDOPT,   TOTXBINARY, TSDATA,     &FQTermTelnet::will_txbinary },
    { TSDOPT,   TCANY,      TSDATA,     &FQTermTelnet::will_notsup },
    { FSINVALID,TCANY,      FSINVALID,  &FQTermTelnet::tnabort },
};


struct fsm_trans FQTermTelnet::substab[] =  {
    /* State        Input       Next State      Action  */
	/* ---------------------------------------------------- */
    { SS_START,     TOTERMTYPE, SS_TERMTYPE,    &FQTermTelnet::no_op },
    { SS_START,     TCANY,      SS_END,         &FQTermTelnet::no_op },
    { SS_TERMTYPE,  TT_SEND,    SS_END,         &FQTermTelnet::subtermtype },
    { SS_TERMTYPE,  TCANY,      SS_END,         &FQTermTelnet::no_op },
    { SS_END,       TCANY,      SS_END,         &FQTermTelnet::no_op },
    { FSINVALID,    TCANY,      FSINVALID,      &FQTermTelnet::tnabort },
};


FQTermTelnet::FQTermTelnet(const QString &strTermType, int rows, int columns, int protocolType, int hostType, const char *sshuser, const char *sshpasswd)
	: from_socket(),
	  to_ansi(),
	  from_ansi(),
	  to_socket(),
	  hostType_(hostType),
      protocolType_(protocolType)
{
	term = new char[21];
	memset(term, 0, 21*sizeof(char));
	// TODO: clean up, need???
#ifdef WIN32
	_snprintf(term, sizeof(term), "%s", strTermType.toLatin1().constData());
#else
	strncpy(term,strTermType.toLatin1(),20);
#endif

	wx = columns;
	wy = rows;
	wsize = 0;
	done_naws = 0;
	synching = 0;
	doecho = 0;
	sndbinary = 0;
	rcvbinary = 0;
	noga = 0;
	termtype = 0;
	naws = 0;
	server_sent_do_naws = 0;
	raw_size = 0;

	if (protocolType == 1) {
		socket = new FQTermSSHSocket(columns, rows, strTermType, sshuser, sshpasswd);
        FQ_VERIFY(connect(socket, SIGNAL(sshAuthOK()), this, SIGNAL(onSSHAuthOK())));
	} else if (protocolType == 2) {
		socket = new FQTermLocalSocket();
	} else {
		socket = new FQTermTelnetSocket();
	}

	// connect signal and slots
	FQ_VERIFY(connect(socket, SIGNAL(connected()), this, SLOT(connected())));
	FQ_VERIFY(connect(socket, SIGNAL(readyRead()), this, SLOT(socketReadyRead())));
	FQ_VERIFY(connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(showError(QAbstractSocket::SocketError))));
	FQ_VERIFY(connect(socket, SIGNAL(hostFound()), this, SLOT(hostFound())));
	FQ_VERIFY(connect(socket, SIGNAL(delayedCloseFinished()),  this, SLOT(delayCloseFinished())));
	FQ_VERIFY(connect(socket, SIGNAL(connectionClosed()), this, SLOT(closed())));
	FQ_VERIFY(connect(socket, SIGNAL(socketState(int)), this, SIGNAL(TelnetState(int))));

	FQ_VERIFY(connect(socket, SIGNAL(requestUserPwd(QString *, QString *, bool *)), 
			  this, SIGNAL(requestUserPwd(QString *, QString *, bool *))));
	FQ_VERIFY(connect(socket, SIGNAL(warnInsecure(const QString &, bool *)),
			  this, SIGNAL(warnInsecure(const QString &, bool *))));

	FQ_VERIFY(connect(socket, SIGNAL(errorMessage(QString)), this, SIGNAL(errorMessage(QString))));

	// Init telnet, mainly the FSMs
	fsmbuild(); /* setup FSMs */
}

FQTermTelnet::~FQTermTelnet()
{
	// delete objects
	delete [] term;
	delete socket;
}

void FQTermTelnet::fsmbuild()
{
	fsminit(ttfsm, ttstab, NTSTATES);
	ttstate = TSDATA;

	fsminit(subfsm, substab, NSSTATES);
	substate = SS_START;

}

void FQTermTelnet::fsminit(u_char fsm[][NCHRS], struct fsm_trans ttab[], int nstates)
{
	struct fsm_trans *pt;
	int sn, ti, cn;
	// 将所有的状态全部设置为无效状态
	for (cn = 0; cn < NCHRS; ++cn) {
		for (ti = 0; ti < nstates; ++ti) {
			fsm[ti][cn] = TINVALID;
		}
	}

	for (ti = 0; ttab[ti].ft_state != FSINVALID; ++ti) {
		pt = &ttab[ti];
		sn = pt->ft_state;
		if (pt->ft_char == TCANY) {
			for (cn = 0; cn < NCHRS; ++cn) {
				if (fsm[sn][cn] == TINVALID) {
					fsm[sn][cn] = ti;
				}
			}
		} else {
			fsm[sn][pt->ft_char] = ti;
		}
	}

	/* set all uninitialized indices to an invalid transition */
	for (cn = 0; cn < NCHRS; ++cn) {
		for (ti = 0; ti < nstates; ++ti) {
			if (fsm[ti][cn] == TINVALID) {
				fsm[ti][cn] = ti;
			}
		}
	}

}

void FQTermTelnet::connectHost(const QString &hostname, quint16 portnumber)
{
	done_naws = 0;
	synching = 0;
	doecho = 0;
	sndbinary = 0;
	rcvbinary = 0;
	noga = 0;
	termtype = 0;
	naws = 0;

	socket->connectToHost(hostname, portnumber);
	// host name resolving
	emit TelnetState(TSRESOLVING);
}

void FQTermTelnet::windowSizeChanged(int x, int y) {
	wx = x;
	wy = y;
	if (bConnected) {
		if (hostType_ == 1 && (protocolType_ == 1 || protocolType_ == 2)) {
			//This is a *nix host, with ssh connection.
			socket->setTermSize(x, y);
			return;
		}
		naws = 0;

		/*
		  QByteArray cmd(10, 0);
		  cmd[0] = (char)TCIAC;
		  cmd[1] = (char)TCSB;
		  cmd[2] = (char)TONAWS;
		  cmd[3] = (char)(short(wx) >> 8);
		  cmd[4] = (char)(short(wx) & 0xff);
		  cmd[5] = (char)(short(wy) >> 8);
		  cmd[6] = (char)(short(wy) & 0xff);
		  cmd[7] = (char)TCIAC;
		  cmd[8] = (char)TCSE;
		  socket->writeBlock(cmd);
		*/

	}
}

void FQTermTelnet::setProxy(int nProxyType, bool bAuth, const QString &strProxyHost, quint16 uProxyPort, const QString &strProxyUsr, const QString &strProxyPwd)
{
    socket->setProxy(nProxyType, bAuth, strProxyHost, uProxyPort, strProxyUsr, strProxyPwd);
}

void FQTermTelnet::close()
{
	socket->close();
}


void FQTermTelnet::connected()
{
	bConnected = true;
	emit TelnetState(TSHOSTCONNECTED);
}

void FQTermTelnet::closed()
{
	bConnected = false;
	emit TelnetState(TSCLOSED);
}

void FQTermTelnet::delayCloseFinished()
{
	bConnected = false;
	emit TelnetState(TSCLOSEFINISH);
}

void FQTermTelnet::hostFound()
{
	emit TelnetState(TSHOSTFOUND);
}

void FQTermTelnet::showError(QAbstractSocket::SocketError index)
{

	switch (index) {
	case QAbstractSocket::ConnectionRefusedError: emit TelnetState(TSREFUSED);
		break;
	case QAbstractSocket::HostNotFoundError: emit TelnetState(TSHOSTNOTFOUND);
		break;
		//FIXME: am I right
	case QAbstractSocket::SocketAccessError: emit TelnetState(TSREADERROR);
		break;
	case QAbstractSocket::RemoteHostClosedError: emit TelnetState(TSCLOSED);
		break;
	default:
		emit TelnetState(TSERROR);
		break;
	}

}

void FQTermTelnet::socketReadyRead()
{
	int nbytes, nread;
	// TODO_UTF16: check this function.
  
	// get the data size
	nbytes = socket->bytesAvailable();
	if (nbytes <= 0) {
		return ;
	}

	raw_size = nbytes;

	//resize input buffer
	from_socket.resize(0);

	//read data from socket to from_socket
	from_socket = socket->readBlock(nbytes);
	nread = from_socket.size();
	//do some checks
	if (nread <= 0) {
		FQ_TRACE("telnet", 8) << "Failed to read socket: " << nread << " <= 0.";
		return ;
	}
	if (nread > nbytes) {
        FQ_TRACE("telnet", 0) << "Overflow when reading socket: " << " nread = " << nread << " > nbytes = " << nbytes;
		return ;
	}

	//resize output buffer
	to_ansi.resize(2 *nread);
	to_socket.resize(4 *nread);

	rsize = 0;
	wsize = 0;

	// do telnet decode job...
	struct fsm_trans *pt;
	int i, ti;

	u_char c;
	for (i = 0; i < nread; ++i) {
        c = (u_char)(from_socket[ i ]);
        ti = ttfsm[ ttstate ][c];
        pt = &ttstab[ ti ];
		(this->*(pt->ft_action))((int)c);
		ttstate = pt->ft_next;
	}

	// flush the to_socket buffer, it contain response to server
	if (wsize > 0) {
		socket->writeBlock(to_socket.left(wsize));
		socket->flush();
	}

	/* send SIGNAL readyRead() with the size of data available*/
	if (rsize > 0 || raw_size > 0) {
		emit readyRead(rsize, raw_size);
	}

}

int FQTermTelnet::raw_len() {
	return raw_size;
}


int FQTermTelnet::read_raw(char *data, uint maxlen) {
	//do some checks
	if (data == 0) {
		FQ_TRACE("telnet", 0) << "NULL pointer.";
		return -1;
	}
	if ((long)maxlen < raw_size) {
		/* we need all data be read out in one read */
		FQ_TRACE("telnet", 0) << "The upper layer's buffer is too small.";
		return -1;
	}

	//do it, memcpy( destination, source, size)
	memcpy(data, from_socket.data(), raw_size);
	return raw_size;
}


int FQTermTelnet::read(char *data, uint maxlen) {
	//do some checks
	if (data == 0) {
		FQ_TRACE("telent", 0) << "NULL pointer.";
		return -1;
	}
	if (maxlen < rsize) {
		/* we need all data be read out in one read */
		FQ_TRACE("telnet", 0) << "The upper layer's buffer is too small.";
		return -1;
	}

	//do it, memcpy( destination, source, size)
	memcpy(data, to_ansi.data(), rsize);

	FQ_TRACE("telnet", 8) << "read " << rsize << " bytes:\n"
			      << dumpHexString << std::string(data, rsize);

	return rsize;
}

int FQTermTelnet::write(const char *data, uint len)
{
	// TODO: accept data, (This seems can be removed????)
	from_ansi.resize(len);
	memcpy(from_ansi.data(), data, len);

	// resize output buffer
	to_socket.resize(2 *len);
	wsize = 0;

	// process keyboard input
	// because we use GUI, there is no need to support a "command mode"
	// So the keyboard-input FSM isn't necessary.

	uint i;

	u_char c; // TODO: for gcc's happy :)
	for (i = 0; i < len; ++i) {
		c = (u_char)(from_ansi[i]);
		soputc((int)c);
	}

    std::string rst_str = to_socket.left(wsize).toStdString();

	//flush socket
	socket->writeBlock(to_socket.left(wsize));
    socket->flush();

	emit TelnetState(TSWRITED);
	return 0;
}

/*------------------------------------------------------------------------
 * tcdm - handle the telnet "DATA MARK" command (marks end of SYNCH)
 *------------------------------------------------------------------------
 */
int FQTermTelnet::tcdm(int) {
	if (synching > 0) {
		synching--;
	}
	return 0;
}

/*------------------------------------------------------------------------
 * rcvurg - receive urgent data input (indicates a telnet SYNCH)
 *------------------------------------------------------------------------
 */
/*
  int FQTermTelnet::rcvurg(int sig)
  {
  synching++;
  }
*/

int FQTermTelnet::recopt(int c) {
	option_cmd = c;
	return 0;
}


int FQTermTelnet::no_op(int) {
	return 0;
}


int FQTermTelnet::do_echo(int c) {
	if (doecho) {
		if (option_cmd == TCWILL) {
			return 0;
		}
		/* already doing ECHO		*/
	} else if (option_cmd == TCWONT) {
		return 0;
	}
	/* already NOT doing ECHO	*/

	doecho = !doecho;

	putc_down(TCIAC);
	if (doecho) {
		putc_down(TCDO);
	} else {
		putc_down(TCDONT);
	}
	putc_down((char)c);
	return 0;
}

/*------------------------------------------------------------------------
 * putc_down - put a character in to_socket buffer.
 * wsize represent the number of bytes in to_socket buffer, and the buffer
 * is addressed from 0, NOT 1.
 *------------------------------------------------------------------------
 */
void FQTermTelnet::putc_down(u_char c) {
	// check overflow
	if ((long)(wsize + 1) > to_socket.size()) {
		FQ_TRACE("telnet", 0) << "Buffer to_socket overflow.";
		return ;
	}
	// put it in the buffer
	//to_socket->replace(wsize, 1, (const char *)&c);
	to_socket[wsize] = c;

	wsize++;
	return ;
}

/*------------------------------------------------------------------------
 * do_notsup - handle an unsupported telnet "will/won't" option
 *------------------------------------------------------------------------
 */

int FQTermTelnet::do_notsup(int c) {
	putc_down(TCIAC);
	putc_down(TCDONT);
	putc_down((char)c);
	return 0;
}

/*------------------------------------------------------------------------
 * do_noga - don't do telnet Go-Ahead's
 *------------------------------------------------------------------------
 */

int FQTermTelnet::do_noga(int c) {
	if (noga) {
		if (option_cmd == TCWILL) {
			return 0;
		}
	} else if (option_cmd == TCWONT) {
		return 0;
	}

	noga = !noga;

	putc_down(TCIAC);
	if (noga) {
		putc_down(TCDO);
	} else {
		putc_down(TCDONT);
	}
	putc_down((char)c);
	return 0;
}

/*------------------------------------------------------------------------
 * do_txbinary - handle telnet "will/won't" TRANSMIT-BINARY option
 *------------------------------------------------------------------------
 */

int FQTermTelnet::do_txbinary(int c) {
	if (rcvbinary) {
		if (option_cmd == TCWILL) {
			return 0;
		}
	} else if (option_cmd == TCWONT) {
		return 0;
	}

	rcvbinary = !rcvbinary;

	putc_down(TCIAC);
	if (rcvbinary) {
		putc_down(TCDO);
	} else {
		putc_down(TCDONT);
	}
	putc_down((char)c);
	return 0;
}

/*------------------------------------------------------------------------
 * will_notsup - handle an unsupported telnet "do/don't" option
 *------------------------------------------------------------------------
 */

int FQTermTelnet::will_notsup(int c) {
	putc_down(TCIAC);
	putc_down(TCWONT);
	putc_down((char)c);
	return 0;
}

/*------------------------------------------------------------------------
 * will_txbinary - handle telnet "do/don't" TRANSMIT-BINARY option
 *------------------------------------------------------------------------
 */

int FQTermTelnet::will_txbinary(int c) {
	if (sndbinary) {
		if (option_cmd == TCDO) {
			return 0;
		}
	} else if (option_cmd == TCDONT) {
		return 0;
	}

	sndbinary = !sndbinary;

	putc_down(TCIAC);
	if (sndbinary) {
		putc_down(TCWILL);
	} else {
		putc_down(TCWONT);
	}
	putc_down((char)c);
	return 0;
}

/*------------------------------------------------------------------------
 * will_termtype - handle telnet "do/don't" TERMINAL-TYPE option
 *------------------------------------------------------------------------
 */
int FQTermTelnet::will_termtype(int c) {
	if (termtype) {
		if (option_cmd == TCDO) {
			return 0;
		}
	} else if (option_cmd == TCDONT) {
		return 0;
	}

	termtype = !termtype;

	putc_down(TCIAC);

	if (termtype) {
		putc_down(TCWILL);
	} else {
		putc_down(TCWONT);
	}

	putc_down((char)c);

	// TODO: Do NOT use this assume! some foolish BBS not response the request
	/* The client expects that once the remote application receives
	   terminal type information it will send control sequences for
	   the terminal, which cannot be sent using the NVT encoding, So
	   change the transfer mode to binary in both directions */
	/* set up binary data path; send WILL, DO */
	/*	if (termtype) {
		option_cmd = TCWILL;
		do_txbinary(TOTXBINARY);
		option_cmd = TCDO;
		will_txbinary(TOTXBINARY);
		}
	*/
	return 0;
}

int FQTermTelnet::will_naws(int c) {
	if (naws) {
		if (option_cmd == TCDO)
			return 0;
	} else if (option_cmd == TCDONT)
		return 0;


	naws = !naws;

	putc_down(TCIAC);
	if (naws)
		putc_down(TCWILL);
	else
		putc_down(TCWONT);
	putc_down((char)c);

	putc_down(TCIAC);
	putc_down(TCSB);
	putc_down(TONAWS);
	putc_down((char)(short(wx) >> 8));
	putc_down((char)(short(wx)&0xff));
	putc_down((char)(short(wy) >> 8));
	putc_down((char)(short(wy)&0xff));
	putc_down(((char)TCIAC));
	putc_down((char)TCSE);

	return 0;
}

/*------------------------------------------------------------------------
 * subopt - do option subnegotiation FSM transitions
 *------------------------------------------------------------------------
 */
int FQTermTelnet::subopt(int c) {
	struct fsm_trans *pt;
	int ti;

	ti = subfsm[substate][c];
	pt = &substab[ti];
	(this->*(pt->ft_action))(c);
	substate = pt->ft_next;
	return 0;
}

/*------------------------------------------------------------------------
 * subtermtype - do terminal type option subnegotation
 *------------------------------------------------------------------------
 */

int FQTermTelnet::subtermtype(int) {
	char *i;
	/* have received IAC.SB.TERMTYPE.SEND */

	putc_down(TCIAC);
	putc_down(TCSB);
	putc_down(TOTERMTYPE);
	putc_down(TT_IS);

	//write term type string
	//fputs(term, sfp);
	for (i = term; (*i) != '\000'; i++) {
		putc_down(*i);
	}

	putc_down(TCIAC);
	putc_down(TCSE);
	return 0;
}

/*------------------------------------------------------------------------
 * subend - end of an option subnegotiation; reset FSM
 *------------------------------------------------------------------------
 */

int FQTermTelnet::subend(int) {
	substate = SS_START;
	return 0;
}

/*------------------------------------------------------------------------
 * soputc - move a character from the keyboard to the socket
 * 	    convert an character into the NVT encoding and send it
 *	    through the socket to the server.
 *------------------------------------------------------------------------
 */

int FQTermTelnet::soputc(int c) {
	if (sndbinary) {
		if (c == TCIAC) {
			putc_down(TCIAC);
		}
		/* byte-stuff IAC	*/
		putc_down(c);
		return 0;
	}

	//c &= 0x7f;	/* 7-bit ASCII only ???*/
	// Convert local special characters to NVT characters
	/* TODO: // BBS don't need control signals
	   if (c == t_intrc || c == t_quitc) {	// Interrupt
	   putc_down(TCIAC);
	   putc_down(TCIP);
	   } else if (c == sg_erase) {		// Erase Char
	   putc_down(TCIAC);
	   putc_down(TCEC);
	   } else if (c == sg_kill) {		// Erase Line
	   putc_down(TCIAC);
	   putc_down(TCEL);
	   } else if (c == t_flushc) {		// Abort Output
	   putc_down(TCIAC);
	   putc_down(TCAO);
	   } else  */
	putc_down((char)c);

	return 0;
}

/*------------------------------------------------------------------------
 * xputc - putc to upper layer with optional file scripting
 *------------------------------------------------------------------------
 */
int FQTermTelnet::xputc_up(char ch) {
	if ((long)(rsize + 1) > to_ansi.size()) {
		FQ_TRACE("telnet", 0) << "Buffer to_ansi overflow.";
		return - 1;
	}
	//to_ansi->replace(wsize, 1, &ch);
	to_ansi[rsize] = u_char(ch);
	rsize++;

	return 0;
}

/*------------------------------------------------------------------------
 * xfputs - fputs with optional file scripting
 *------------------------------------------------------------------------
 */
int FQTermTelnet::xputs_up(char *str) {
	/*if (scrfp)
	  fputs(str, scrfp);*/

	char *i;
	for (i = str; (*i) != '\000'; i++) {
		xputc_up(*i);
	}
	return 0;
}

/*------------------------------------------------------------------------
 * ttputc - print a single character on a Network Virtual Terminal
 *------------------------------------------------------------------------
 */
int FQTermTelnet::ttputc(int c) {
	if (rcvbinary) {
		xputc_up((char)c); /* print uninterpretted	*/
		return 0;
	}
	/* no data, if in SYNCH */
	/*
	  if (synching)
	  return 0;
	*/
	/* TODO: FQTermTelnet does not interpret NVT code, provide data to upper
	   layer directly. So, <cr><lf> will not be replaced with <lf>
	*/
	xputc_up((char)c);

	return 0;
}

/*------------------------------------------------------------------------
 * invalid state reached, aborted
 *------------------------------------------------------------------------
 */
int FQTermTelnet::tnabort(int)
{
	FQ_VERIFY(false); // "invalid state reached, aborted";
	//	exit(-1);
	return -1;
}

bool FQTermTelnet::readyForInput()
{
	return socket->readyForInput();
}


}  // namespace FQTerm

